﻿<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:jsx="http://www.codeplex.com/JSClientPages/Compiler">
    <!--
        The JavaScriptClientPages compiler - written in XSLT 1.0
        
        This stylesheet provides text replacement functionality that makes it possible
        to escape content that does not mix well with JavaScript strings        
        
        Copyright (c) 2008, Artem Ploujnikov
        All rights reserved.

            Redistribution and use in source and binary forms, with or without modification, are permitted provided 
            that the following conditions are met:

        * Redistributions of source code must retain the above copyright notice, this list of conditions and 
        the following disclaimer.

        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
        the following disclaimer in the documentation and/or other materials provided with the distribution.

        * Neither the name of Artem Ploujnikov nor the names of its contributors may be used to endorse or promote 
        products derived from this software without specific prior written permission.

        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
        WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
        PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
        ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
        LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
        OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
        STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
        THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
    -->

    <!--
        Replaces all occurences of the "search" parameter in the text provided
        using the "text" parameter with the value of the "replace" parameter.
        
        Parameters:
        
        text:     the original text
        search:   the search string
        replace:  the text with which occurrences of the search string will be replaced
        
        Example:
        
        <xsl:call-template name="string-replace">
            <xsl:with-param name="string">It is hard to find a needle in a haystack</xsl:with-param>
            <xsl:with-param name="search">needle</xsl:with-param>
            <xsl:with-param name="replace">haystack</xsl:with-param
        </xsl:call-template>
        
        Result:
        It is hard to find a haystack in a haystack.
    -->
    <xsl:template name="replace-string">
        <xsl:param name="text" />
        <xsl:param name="search" />
        <xsl:param name="replace" />
        <xsl:choose>
            <xsl:when test="contains($text, $search)">
                <xsl:variable name="tail">
                    <xsl:call-template name="replace-string">
                        <xsl:with-param name="text" select="substring-after($text, $search)" />
                        <xsl:with-param name="search" select="$search" />
                        <xsl:with-param name="replace" select="$replace" />
                    </xsl:call-template>
                </xsl:variable>
                <xsl:value-of select="concat(substring-before($text, $search), $replace, $tail) "/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$text"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!--
        Performs a context-based text replacement. A "context" refers
        to a scenario where a specific series of replacements is necessary
        
        Contexts are represented by <jsx:replace> tags with a "context" attribute
        
        Parameters:
        
        text: the origonal text
        context: one of the replacement contexts defined in the stylesheet document
        
        Example:

        <jsx:replace context="animals">
            <jsx:entry>
                <jsx:match>dog</jsx:match>
                <jsx:replace>cat</jsx:replace>
            </jsx:entry>
            <jsx:entry>
                <jsx:match>fox</jsx:match>
                <jsx:replace>rabbit</jsx:replace>
            </jsx:entry>
        </jsx:replace>
            

        <xsl:template name="example">         
            <xsl:call-template name="replace-context">
                <xsl:param name="context">animals</xsl:param>
                <xsl:param name="text">A quick brown fox jumps over the lazy dog</xsl:param>
            </xsl:call-template>
        </xsl:template>
    
        The output is:
        
        A quick brown rabbit jumps over the lazy cat
    -->
    <xsl:template name="replace-context">
        <xsl:param name="text" />
        <xsl:param name="context" />
        <xsl:variable name="context-element" select="document('')/descendant::jsx:replace[@context=$context]" />

        <xsl:apply-templates select="$context-element" mode="recursive-replace">
            <xsl:with-param name="text" select="$text" />
        </xsl:apply-templates>
    </xsl:template>

    <!--
        The top-level template for <jsx:replace>
    -->
    <xsl:template match="jsx:replace" mode="recursive-replace">
        <xsl:param name="text" />
        <xsl:apply-templates select="jsx:entry[position() = last()]"  mode="recursive-replace">
            <xsl:with-param name="text" select="$text" />
        </xsl:apply-templates>
    </xsl:template>

    <!--
        The recursive case for <jsx:entry> - processes the previous item
        from the sequence and performs the replacement on the result
    -->
    <xsl:template match="jsx:entry"  mode="recursive-replace">
        <xsl:param name="text" />
        <xsl:variable name="recursive-result">
            <xsl:apply-templates select="preceding-sibling::jsx:entry[position() = last()]" mode="recursive-replace">
                <xsl:with-param name="text" select="$text" />
            </xsl:apply-templates>
        </xsl:variable>

        <xsl:call-template name="replace-string">
            <xsl:with-param name="text" select="$recursive-result" />
            <xsl:with-param name="search" select="jsx:match" />
            <xsl:with-param name="replace" select="jsx:replace" />
        </xsl:call-template>
    </xsl:template>

    <!--
        The base case for <jsx:entry> - performs the replacement
        on the text and returns the result.
    -->
    <xsl:template match="jsx:entry[position() = 1]"  mode="recursive-replace">
        <xsl:param name="text" />
        <xsl:call-template name="replace-string">
            <xsl:with-param name="text" select="$text" />
            <xsl:with-param name="search" select="jsx:match" />
            <xsl:with-param name="replace" select="jsx:replace" />
        </xsl:call-template>
    </xsl:template>


    <jsx:replace context="js-text">
        <jsx:entry>
            <jsx:match>&#xa;</jsx:match>
            <jsx:replace>' + "\n" + '</jsx:replace>
        </jsx:entry>
        <jsx:entry>
            <jsx:match>\</jsx:match>
            <jsx:replace>\\</jsx:replace>
        </jsx:entry>
        <jsx:entry>
            <jsx:match>'</jsx:match>
            <jsx:replace>\'</jsx:replace>
        </jsx:entry>        
    </jsx:replace>
</xsl:stylesheet>
